package com.xy.tool.report;

import java.util.*;
import java.util.function.Supplier;

/**
 * Created by zsp on 2018/12/6.
 */
public class Crosstab<THId, THSort, TVId, TVSort> {

    private List<Header<THId, THSort>> horizontalHeaderList;
    private List<Header<TVId, TVSort>> verticalHeaderList;
    private Comparator<Header<THId, THSort>> horizontalSorter;
    private Comparator<Header<TVId, TVSort>> verticalSorter;
    private Map<THId, Header<THId, THSort>> horizontalHeaderMap;
    private Map<TVId, Header<TVId, TVSort>> verticalHeaderMap;
    private Map<Coord<THId, TVId>, Integer> numberMap;
    private int[][] data;

    public Crosstab() {
        horizontalHeaderList = new ArrayList<>();
        verticalHeaderList = new ArrayList<>();
        horizontalHeaderMap = new HashMap<>();
        verticalHeaderMap = new HashMap<>();
        numberMap = new HashMap<>();
    }

    public Crosstab(Comparator<Header<THId, THSort>> horizontalSorter,
                    Comparator<Header<TVId, TVSort>> verticalSorter) {
        this();
        this.horizontalSorter = horizontalSorter;
        this.verticalSorter = verticalSorter;
    }

    public boolean addHorizontalHeaderIfAbsent(THId id, Supplier<Header<THId, THSort>> creator) {
        if (!horizontalHeaderMap.containsKey(id)) {
            Header<THId, THSort> header = creator.get();
            horizontalHeaderMap.put(id, header);
            horizontalHeaderList.add(header);
            return true;
        }
        return false;
    }

    public Header<THId, THSort> getHorizontalHeader(THId id, Supplier<Header<THId, THSort>> creator) {
        Header<THId, THSort> header = horizontalHeaderMap.get(id);
        if(header == null) {
            header = creator.get();
            horizontalHeaderMap.put(id, header);
            horizontalHeaderList.add(header);
        }
        return header;
    }

    public boolean addVerticalHeaderIfAbsent(TVId id, Supplier<Header<TVId, TVSort>> creator) {
        if (!verticalHeaderMap.containsKey(id)) {
            Header<TVId, TVSort> header = creator.get();
            verticalHeaderMap.put(id, header);
            verticalHeaderList.add(header);
            return true;
        }
        return false;
    }

    public Header<TVId, TVSort> getVerticalHeader(TVId id, Supplier<Header<TVId, TVSort>> creator) {
        Header<TVId, TVSort> header = verticalHeaderMap.get(id);
        if(header == null) {
            header = creator.get();
            verticalHeaderMap.put(id, header);
            verticalHeaderList.add(header);
        }
        return header;
    }

    public void add(Coord<THId, TVId> coord, int number) {
        Integer found = numberMap.get(coord);
        if (found != null) {
            numberMap.put(coord, found.intValue() + number);
        } else {
            numberMap.put(coord, number);
        }
    }

    public void fill() {
        sort();

        int colSize = horizontalHeaderList.size();
        int rowSize = verticalHeaderList.size();
        data = new int[rowSize][colSize];
        THId colId;
        TVId rowId;
        Coord<THId, TVId> coord = new Coord();
        for (int row = 0; row < rowSize; row++) {
            for (int col = 0; col < colSize; col++) {
                colId = horizontalHeaderList.get(col).getId();
                rowId = verticalHeaderList.get(row).getId();
                Integer number = numberMap.get(coord.ofCol(colId).ofRow(rowId));
                if (number == null) {
                    data[row][col] = 0;
                } else {
                    data[row][col] = number.intValue();
                }
            }
        }
    }

    public int[] sumHorizontally() {
        return sumHorizontally(0, horizontalHeaderList.size());
    }

    public int[] sumHorizontally(int startIndex) {
        return sumHorizontally(startIndex, horizontalHeaderList.size());
    }

    public int[] sumHorizontally(int startIndex, int endIndex) {
        if (startIndex < 0 || endIndex < 1 || startIndex > endIndex) {
            throw new IllegalArgumentException();
        }
        int colSize = horizontalHeaderList.size();
        if (startIndex > colSize) {
            return new int[0];
        }
        int startCol = startIndex;
        int endCol = endIndex > colSize ? colSize : endIndex;
        int rowSize = verticalHeaderList.size();
        int number;
        int[] sum = new int[rowSize];
        for (int row = 0; row < rowSize; row++) {
            number = 0;
            for (int col = startCol; col < endCol; col++) {
                number = number + data[row][col];
            }
            sum[row] = number;
        }
        return sum;
    }

    public int[] sumVertically() {
        return sumVertically(0, verticalHeaderList.size());
    }

    public int[] sumVertically(int startIndex) {
        return sumVertically(startIndex, verticalHeaderList.size());
    }

    public int[] sumVertically(int startIndex, int endIndex) {
        return null;
    }

    private void sort() {
        if (horizontalSorter != null) {
            horizontalHeaderList.sort(horizontalSorter);
        }
        if (verticalSorter != null) {
            verticalHeaderList.sort(verticalSorter);
        }
    }

    public int[][] getData() {
        return data;
    }

    public int getColSize() {
        return horizontalHeaderList.size();
    }

    public int getRowSize() {
        return verticalHeaderList.size();
    }

    public String[] getHorizontalHeaderNames() {
        int size = horizontalHeaderList.size();
        String[] names = new String[size];
        for(int i = 0; i < size; i++) {
            names[i] = horizontalHeaderList.get(i).getName();
        }
        return names;
    }

    public String[] getVerticalHeaderNames() {
        int size = verticalHeaderList.size();
        String[] names = new String[size];
        for(int i = 0; i < size; i++) {
            names[i] = verticalHeaderList.get(i).getName();
        }
        return names;
    }

    public List<Header<THId, THSort>> getHorizontalHeaders() {
        return horizontalHeaderList;
    }

    public List<Header<TVId, TVSort>> getVerticalHeaders() {
        return verticalHeaderList;
    }

}
